package com.flow.framework.common.stream;

import com.flow.framework.common.constant.FrameworkCommonConstant;
import com.flow.framework.common.stream.handler.BatchReturnProcessHandler;
import com.flow.framework.common.util.io.IoUtil;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.IOException;
import java.io.OutputStream;

/**
 * 批量编码输出流包装
 *
 * @author luoguopiao
 * @version 0.0.1
 * @date 2022/1/2
 */
public class BatchProcessOutputStream extends OutputStream {

    private final OutputStream finalOutputStream;

    private final BatchReturnProcessHandler batchReturnProcessHandler;

    private final ProcessCache processCache = new ProcessCache();

    public BatchProcessOutputStream(OutputStream srcOutputStream, BatchReturnProcessHandler batchReturnProcessHandler) {
        IoUtil.checkOutputStreamAndHandler(srcOutputStream, batchReturnProcessHandler);
        this.finalOutputStream = srcOutputStream;
        this.batchReturnProcessHandler = batchReturnProcessHandler;
    }

    @Override
    public void write(byte[] buffer) throws IOException {
        handleWrite(buffer, 0, buffer.length);
    }

    private void handleWrite(byte[] targetBuffer, int off, int len) throws IOException {
        // 缓存buffer
        byte[] cacheBuffer = processCache.getBuffer();

        // 当前缓存的真实索引
        int cacheCurrent = processCache.getCurrent();

        // 当前缓存的有效长度
        int cacheLength = processCache.getLength();

        // 批量处理的长度
        int batchSize = batchReturnProcessHandler.getBatchSize();

        // 目标对象的数组有效长度
        int targetBufferLength = len;

        // 目标数组当前索引
        int targetBufferCurrent = off;

        // 如果批量处理长度等于目标数组长度且目标数组原本长度等于目标数组有效长度并且缓存数组有效长度等于0，
        // 则直接开始编码并写入输出流
        if (batchSize == targetBufferLength && targetBuffer.length == targetBufferLength && 0 == cacheLength) {
            // 将需要写出的数据复制后调用批量处理器
            byte[] srcTempBuffer = new byte[batchSize];
            System.arraycopy(targetBuffer, 0, srcTempBuffer, 0, batchSize);
            byte[] handledBytes = batchReturnProcessHandler.process(srcTempBuffer);
            finalOutputStream.write(handledBytes);
            return;
        }

        // 如果缓存数组为空则创建和批量处理大小相等的缓存数组
        if (null == cacheBuffer) {
            cacheBuffer = new byte[batchSize];
            processCache.setBuffer(cacheBuffer);
        }

        // 缓存buffer的实际长度减去有效长度等于还可以容纳的长度和目标buffer有效长度相比取最小值作为需要复制的数据长度
        int copyLength = Math.min(cacheBuffer.length - cacheLength, targetBufferLength);
        if (0 != copyLength) {
            System.arraycopy(targetBuffer, targetBufferCurrent, cacheBuffer, cacheCurrent, copyLength);

            // 缓存数组的当前索引加上复制的数据长度则等于下一个数据写入位置的索引
            cacheCurrent += copyLength;
            processCache.setCurrent(cacheCurrent);

            // 缓存数组的有效长度加上复制的数据长度则等于缓存最新的有效长度
            cacheLength += copyLength;
            processCache.setLength(cacheLength);

            // 目标buffer的有效长度减去复制的数据长度等于最新的有效数据长度
            targetBufferLength -= copyLength;

            // 目标buffer的索引减去复制的数据长度等于最新的下一个需要复制的数据的索引
            targetBufferCurrent += copyLength;
        }

        // 如果缓存数组的有效成都等于批量编码的大小，则调研编码处理器进行编码
        if (cacheLength == batchSize) {
            // 将需要写出的数据复制后调用批量处理器
            byte[] srcTempBuffer = new byte[batchSize];
            System.arraycopy(cacheBuffer, 0, srcTempBuffer, 0, batchSize);
            byte[] handledBytes = batchReturnProcessHandler.process(srcTempBuffer);
            finalOutputStream.write(handledBytes);
            processCache.setCurrent(0);
            processCache.setLength(0);
        }

        // 如果目标对象的有效长度不等于0，则需要继续递归调用编码
        if (0 != targetBufferLength) {
            // 递归调用，可能会存在多次，实际调用取决于原始需要写出的数组数据大小和批处理数据的批量大小
            // 即：原始数组数据大小和批处理数据的批量大小除以他们所有公约数之后的两个数的乘积，则为递归调用的次数
            handleWrite(targetBuffer, targetBufferCurrent, targetBufferLength);
        }
    }


    @Override
    public void write(byte[] buffer, int off, int len) throws IOException {
        handleWrite(buffer, off, len);
    }

    @Override
    public void flush() throws IOException {
        int length = processCache.getLength();
        if (0 != length) {
            byte[] tempBuffer = new byte[length];
            System.arraycopy(processCache.getBuffer(), processCache.getCurrent(), tempBuffer, 0, length);
            byte[] handledBytes = batchReturnProcessHandler.process(tempBuffer);
            finalOutputStream.write(handledBytes);
        }
        finalOutputStream.flush();
    }

    @Override
    public void close() throws IOException {
        IoUtil.close(finalOutputStream);
        super.close();
    }

    @Override
    public void write(int b) throws IOException {
        handleWrite(new byte[]{(byte) b}, 0, FrameworkCommonConstant.ONE_ELEMENT_COLLECTION_SIZE);
    }

    @Data
    @NoArgsConstructor
    private static class ProcessCache {
        private byte[] buffer;

        /**
         * 当前下一个缓存的数组位置
         */
        private int current;

        /**
         * 当前缓存数组中的有效数据长度，从第0号位置开始算
         */
        private int length;
    }
}